package com.sandyz.virtualcam.hooks

import android.content.res.XModuleResources
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.SurfaceTexture
import android.hardware.Camera
import android.hardware.Camera.PreviewCallback
import android.os.Build
import android.os.Handler
import android.view.PixelCopy
import android.view.Surface
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.View
import android.widget.FrameLayout
import com.sandyz.virtualcam.jni.EncoderJNI
import com.sandyz.virtualcam.utils.HookUtils
import com.sandyz.virtualcam.utils.PlayIjk
import com.sandyz.virtualcam.utils.xLog
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import tv.danmaku.ijk.media.player.IjkMediaPlayer
import kotlin.math.min

/**
 * 拼多多虚拟摄像头实现
 * 
 * 主要功能：
 * 1. 视频流注入
 * 2. 预览帧处理
 * 3. 图像格式转换
 * 4. 播放器管理
 * 
 * 实现细节：
 * - 支持拼多多应用
 * - 使用ijkplayer播放视频
 * - YUV格式转换
 * - 帧率控制
 * - 空Surface处理
 * 
 * @author sandyz987
 * @date 2023/12/15
 */
class VirtualCameraPdd : IHook {
    // 1. 基本信息
    override fun getName(): String = "拼多多能用的虚拟摄像头"
    override fun getSupportedPackages() = listOf(
        "com.xunmeng.pinduoduo",  // 拼多多
    )

    // 2. 初始化
    override fun init(cl: ClassLoader?) {
    }

    override fun registerRes(moduleRes: XModuleResources?) {
    }

    // 3. 视频参数
    private val fps = 30  // 帧率
    private var lastDrawTimestamp = 0L  // 上一帧时间戳
    private var previewCallbackClazz: Class<*>? = null  // 预览回调类
    private var virtualSurfaceView: SurfaceView? = null  // 虚拟预览视图
    private var camera: Camera? = null  // 相机实例

    // 4. 图像参数
    @Volatile
    private var width = 0  // 预览宽度
    @Volatile
    private var height = 0  // 预览高度
    @Volatile
    private var yuvByteArray: ByteArray? = null  // YUV数据
    private var drawJob: Job? = null  // 绘制任务
    private var bitmap: Bitmap? = null  // 位图缓存
    private var ijkMediaPlayer: IjkMediaPlayer? = null  // 播放器实例

    // 5. Surface管理
    private var nullSurface: Surface? = null  // 空Surface
    private var nullSurfaceTex: SurfaceTexture? = null  // 空Surface纹理

    /**
     * 5.1 重置Surface
     * 
     * 处理流程：
     * 5.1.1 释放旧Surface
     * 5.1.2 创建新Surface
     */
    private fun resetSurface() {
        nullSurfaceTex?.release()
        nullSurface?.release()
        nullSurfaceTex = SurfaceTexture(15)
        nullSurface = Surface(nullSurfaceTex)
    }

    /**
     * 6. Hook实现
     * 
     * 处理流程：
     * 6.1 Hook相机2 API
     * 6.2 Hook相机预览方法
     * 6.3 Hook预览回调
     * 6.4 处理预览帧
     */
    override fun hook(lpparam: LoadPackageParam?) {
        // 6.0 检查应用包名，只对支持的应用进行hook
        val packageName = lpparam?.packageName
        if (packageName !in getSupportedPackages()) {
            xLog("应用 $packageName 不在支持列表中，跳过hook")
            return
        }
        xLog("开始hook应用: $packageName")

        // 6.1 Hook相机2 API
        XposedHelpers.findAndHookMethod("android.hardware.camera2.CaptureRequest.Builder", lpparam?.classLoader, "addTarget", Surface::class.java, object : XC_MethodHook() {
            override fun beforeHookedMethod(param: MethodHookParam?) {
                // 将相机输出重定向到空Surface
                // 把相机向应用程序输出的内容定向为虚无
                // 因为createCaptureSession只添加了一个nullSurface，所以addTarget必须只能向nullSurface输出
                resetSurface()
                param?.args?.set(0, nullSurface)
            }
        })

        // 6.2 Hook开始预览
        XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam?.classLoader, "startPreview", object : XC_MethodHook() {
            override fun beforeHookedMethod(param: MethodHookParam?) {
                xLog("应用程序开始预览startPreview    topActivity:${HookUtils.getTopActivity()}")
                stopPreview()
                startPreview()
                HookUtils.dumpView(HookUtils.getContentView(), 0)
            }
        })

        // 6.3 Hook停止预览
        XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam?.classLoader, "stopPreview", object : XC_MethodHook() {
            override fun beforeHookedMethod(param: MethodHookParam?) {
                xLog("应用程序停止预览stopPreview   ")
                stopPreview()
            }
        })

        // 6.4 Hook带缓冲的预览回调
        XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam?.classLoader, "setPreviewCallbackWithBuffer", PreviewCallback::class.java, object : XC_MethodHook() {
            override fun beforeHookedMethod(param: MethodHookParam) {
                if (previewCallbackClazz == null) {
                    previewCallbackClazz = param.args[0].javaClass
                    XposedHelpers.findAndHookMethod(previewCallbackClazz, "onPreviewFrame", ByteArray::class.java, Camera::class.java, object : XC_MethodHook() {
                        override fun beforeHookedMethod(param: MethodHookParam?) {
                            if (drawJob?.isActive != true) return
                            camera = param?.args?.get(1) as Camera
                            width = camera?.parameters?.previewSize?.width ?: 0
                            height = camera?.parameters?.previewSize?.height ?: 0
                            xLog("onPreviewFrameWithBuffer             package:${lpparam?.packageName}          process:${lpparam?.processName}          bytearray:${param.args?.get(0)}")
                            if (yuvByteArray != null) {
                                val byteArray = param.args?.get(0) as ByteArray
                                // 复制YUV数据
                                yuvByteArray?.let {
                                    System.arraycopy(it, 0, byteArray, 0, min(byteArray.size, it.size))
                                }
                            }
                        }
                    })
                }
            }
        })

        // 6.5 Hook普通预览回调
        XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam?.classLoader, "setPreviewCallback", PreviewCallback::class.java, object : XC_MethodHook() {
            override fun beforeHookedMethod(param: MethodHookParam) {
                if (previewCallbackClazz == null) {
                    previewCallbackClazz = param.args[0].javaClass
                    XposedHelpers.findAndHookMethod(previewCallbackClazz, "onPreviewFrame", ByteArray::class.java, Camera::class.java, object : XC_MethodHook() {
                        override fun beforeHookedMethod(param: MethodHookParam?) {
                            xLog("onPreviewFrame             package:${lpparam?.packageName}          process:${lpparam?.processName}          bytearray:${param?.args?.get(0)}")
                            // 清空预览数据
                            val byteArray = param?.args?.get(0) as ByteArray
                            byteArray.forEachIndexed { index, _ ->
                                byteArray[index] = 0
                            }
                        }
                    })
                }
            }
        })
    }

    /**
     * 7. 绘制循环
     * 
     * 处理流程：
     * 7.1 获取播放器图像
     * 7.2 转换为YUV格式
     * 7.3 控制帧率
     */
    private suspend fun drawer() {
        while (true) {
            try {
                if (width == 0 || height == 0) {
                    delay(1000L / fps.toLong())
                    continue
                }
                lastDrawTimestamp = System.currentTimeMillis()
                
                // 7.1 获取播放器图像
                bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
                virtualSurfaceView?.let {
                    getBitmapByView(it)
                }
                bitmap = getRotateBitmap(bitmap, -90f, width, height)
                xLog("rotatedbitmap:$bitmap, width:${bitmap?.width}, height:${bitmap?.height}")
                
                // 7.2 转换为YUV格式
                yuvByteArray = bitmap?.let { bitmapToYuv((it), width, height) }

                // 7.3 控制帧率
                xLog("复制Surface内容耗时: ${System.currentTimeMillis() - lastDrawTimestamp} bitmap:$bitmap, width:$width, height:$height")
                if (System.currentTimeMillis() < lastDrawTimestamp + 1000L / fps.toLong())
                    delay(lastDrawTimestamp + 1000L / fps.toLong() - System.currentTimeMillis())
            } catch (e: IllegalArgumentException) {
                xLog("exception:${e}")
                e.printStackTrace()
                stopPreview()
            }
        }
    }

    /**
     * 8. 图像处理工具
     */
    
    /**
     * 8.1 旋转位图
     * 
     * @param bitmap 源位图
     * @param rotateDegree 旋转角度
     * @param width 目标宽度
     * @param height 目标高度
     * @return Bitmap? 旋转后的位图
     */
    private fun getRotateBitmap(bitmap: Bitmap?, rotateDegree: Float, width: Int, height: Int): Bitmap? {
        bitmap ?: return null
        val matrix = Matrix()
        matrix.postRotate(rotateDegree)
        matrix.postScale(width / height.toFloat(), height / width.toFloat())
        return Bitmap.createBitmap(
            bitmap, 0, 0, bitmap.width,
            bitmap.height, matrix, false
        )
    }

    /**
     * 8.2 创建播放器
     * 
     * @return IjkMediaPlayer 播放器实例
     */
    private fun newIjkMediaPlayer(): IjkMediaPlayer = IjkMediaPlayer {} // 已经加载库了就不加载了

    /**
     * 8.3 重置播放器
     */
    private fun resetIjkMediaPlayer() {
        if (ijkMediaPlayer?.isPlaying == true) {
            ijkMediaPlayer?.stop()
        }
        ijkMediaPlayer?.release()
        ijkMediaPlayer = newIjkMediaPlayer()
    }

    /**
     * 9. 预览控制
     */
    
    /**
     * 9.1 开始预览
     * 
     * 处理流程：
     * 9.1.1 启动绘制任务
     * 9.1.2 创建预览视图
     * 9.1.3 设置播放器
     */
    private fun startPreview() {
        xLog("开启预览线程1")
        drawJob = HookUtils.coroutineScope().launch {
            drawer()
        }
        if (virtualSurfaceView == null) {
            virtualSurfaceView = SurfaceView(HookUtils.getTopActivity())
            HookUtils.getTopActivity()?.runOnUiThread {
                virtualSurfaceView ?: return@runOnUiThread
                HookUtils.getContentView()?.addView(virtualSurfaceView)
                HookUtils.getContentView()?.getChildAt(0)?.bringToFront()
                virtualSurfaceView?.layoutParams = FrameLayout.LayoutParams(2, 2)
            }
            virtualSurfaceView?.visibility = View.VISIBLE
            virtualSurfaceView?.holder?.addCallback(object : SurfaceHolder.Callback {
                override fun surfaceCreated(holder: SurfaceHolder) {
                    resetIjkMediaPlayer()
                    PlayIjk.play(holder.surface, ijkMediaPlayer)
                }

                override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
                }

                override fun surfaceDestroyed(holder: SurfaceHolder) {
                }
            })
        } else {
            resetIjkMediaPlayer()
            PlayIjk.play(virtualSurfaceView?.holder?.surface, ijkMediaPlayer)
        }
    }

    /**
     * 9.2 停止预览
     * 
     * 处理流程：
     * 9.2.1 取消绘制任务
     * 9.2.2 重置播放器
     * 9.2.3 移除预览视图
     */
    private fun stopPreview() {
        drawJob?.cancel()
        resetIjkMediaPlayer()
        if (virtualSurfaceView != null) {
            HookUtils.getTopActivity()?.runOnUiThread {
                HookUtils.getContentView()?.removeView(virtualSurfaceView)
            }
            virtualSurfaceView = null
        }
    }

    private fun bitmapToYuv(bitmap: Bitmap, width: Int, height: Int): ByteArray {
        val intArray = IntArray(width * height)
        bitmap.getPixels(intArray, 0, width, 0, 0, width, height)
//        val yuvByteArray = ByteArray(width * height * 3 / 2)
//        encodeYUV420SP(yuvByteArray, intArray, width, height)
        return EncoderJNI.encodeYUV420SP(intArray, width, height)

    }

    // https://stackoverflow.com/questions/5960247/convert-bitmap-array-to-yuv-ycbcr-nv21
    // ps: Why does copilot know that I copied this code from stackoverflow?
    // 改native性能应该可以优化一点，现在有点卡
    private fun encodeYUV420SP(yuv420sp: ByteArray, argb: IntArray, width: Int, height: Int) {
        val frameSize = width * height
        var yIndex = 0
        var uvIndex = frameSize
        var a: Int
        var R: Int
        var G: Int
        var B: Int
        var Y: Int
        var U: Int
        var V: Int
        var index = 0
        for (j in 0 until height) {
            for (i in 0 until width) {
                a = argb[index] and -0x1000000 shr 24 // a is not used obviously
                R = argb[index] and 0xff0000 shr 16
                G = argb[index] and 0xff00 shr 8
                B = argb[index] and 0xff shr 0

                // well known RGB to YUV algorithm
                Y = (66 * R + 129 * G + 25 * B + 128 shr 8) + 16
                U = (-38 * R - 74 * G + 112 * B + 128 shr 8) + 128
                V = (112 * R - 94 * G - 18 * B + 128 shr 8) + 128

                // NV21 has a plane of Y and interleaved planes of VU each sampled by a factor of 2
                //    meaning for every 4 Y pixels there are 1 V and 1 U.  Note the sampling is every other
                //    pixel AND every other scanline.
                yuv420sp[yIndex++] = (if (Y < 0) 0 else if (Y > 255) 255 else Y).toByte()
                if (j % 2 == 0 && index % 2 == 0) {
                    yuv420sp[uvIndex++] = (if (V < 0) 0 else if (V > 255) 255 else V).toByte()
                    yuv420sp[uvIndex++] = (if (U < 0) 0 else if (U > 255) 255 else U).toByte()
                }
                index++
            }
        }
    }

    private fun getBitmapByView(surfaceView: SurfaceView) {
        val bitmap = bitmap ?: return
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            // 同步或者异步，不同设备可能不同
            PixelCopy.request(surfaceView, bitmap, { copyResult ->
                if (copyResult == PixelCopy.SUCCESS) {
                    xLog("get bitmap success")
                } else {
                    xLog("get bitmap failed")
                }
            }, Handler(HookUtils.getTopActivity()?.mainLooper!!))
        }
    }

}